#include "bib_Hilo_Principal.h"

extern struct EST_DISCO disco;
extern struct EST_CHS poscicionCabezal;

extern struct EST_PEDIDOS *primerPedido;
extern struct EST_PEDIDOS *ultimoPedido;

extern pthread_mutex_t mutexLog;
extern pthread_mutex_t mutexCola;
extern pthread_mutex_t mutexSocket;

extern int sockExterior;
extern int sockConsola;

extern char flagFin;
extern char modoInicio;

extern char *persistencia;
extern uint32_t cantEscrituras;
extern size_t tamanio;

void verificarCola(void *ptr);
int finalizarConexion(int sock, fd_set *sockets, int *sockMax);
int almacenarPedido(int sock, fd_set *sockets, int *sockMax);

int establecerConexionExterior(struct CONFIG_EST *config){

	/*struct sockaddr address;*/

	if (modoInicio == CONNECT){ /*Conexion con PRAID*/
		if (!abrirSocketCliente(&sockExterior, config->puerto, config->ip))
			if (handshakePRAID(&(config->idDisco))){
				cerrarSocket(&sockExterior);
				imprimirLog(WARNING, "Handshake fallido con PRAID", "");
			}
	}

	else /*Conexion con PFS*/
		if (abrirSocketServidor(&sockExterior, config->puerto))
			sockExterior = 0;
			/*if (!aceptarConexion(&sockEscuchaExterior, &sockExterior, &address))
				if (handshakePFS()){
					cerrarSocket(&sockExterior);
					imprimirLog(WARNING, "Handshake fallido con PFS", "");
				}

			cerrarSocket(&sockEscuchaExterior);*/

	return FIN_OK;
}
/*=====================================================================================*/
int handshakePRAID(uint8_t *idDisco){

	struct HEADER_NIPC header;
	char *payload;

	header.type = TIPO_HANDSHAKE;
	header.payloadLength = sizeof(uint8_t);

	if (enviarMensajeNIPC(sockExterior, &header, (char *) idDisco))
		return FIN_ERROR;

	if (recibirMensajeNIPCDinamico(sockExterior, &header, &payload))
		return FIN_ERROR;

	if ((header.type != TIPO_HANDSHAKE) || (header.payloadLength)){
		if (payload)
			free(payload);
		return FIN_ERROR;
	}

	return FIN_OK;
}
/*=====================================================================================*/
int handshakePFS(void){

	struct HEADER_NIPC header;
	char *payload;

	if (recibirMensajeNIPCDinamico(sockExterior, &header, &payload))
		return FIN_ERROR;

	if ((header.type != TIPO_HANDSHAKE) || (header.payloadLength)){
		if (payload)
			free(payload);
		return FIN_ERROR;
	}

	header.type = TIPO_HANDSHAKE;
	header.payloadLength = 0;

	if (enviarMensajeNIPC(sockExterior, &header, NULL))
		return FIN_ERROR;

	return FIN_OK;
}
/*=====================================================================================*/
int crearHijoConsola(void){

	pid_t pid;

	if ((pid = fork()) == -1){
		imprimirLog(WARNING, "No se pudo crear el hijo consola.", "");
		return FIN_WARNING;
	}

	if (!pid)
		if (execl("../consolaPPD/Debug/consolaPPD", "", NULL) == -1){
			imprimirLog(ERROR, "execl:", strerror(errno));
			return FIN_ERROR;
		}

	return FIN_OK;
}
/*=====================================================================================*/
int inicializarVariables(int *sockMax, fd_set *sockets){

	if (!sockConsola && !sockExterior){
		imprimirLog(ERROR, "No hay comunicacion con el PRAID/PFS, ni con la consola. Fin de programa", "");
		return FIN_ERROR;
	}

	if (pthread_mutex_init(&mutexCola, NULL)){
		imprimirLog(ERROR, "No se pudo crear el mutex para la cola de pedidos", "");
		return FIN_ERROR;
	}

	if (pthread_mutex_init(&mutexSocket, NULL)){
		imprimirLog(ERROR, "No se pudo crear el mutex para los sockets", "");
		return FIN_ERROR;
	}

	primerPedido = NULL;
	ultimoPedido = NULL;

	cantEscrituras = 0;

	poscicionCabezal.cabezal = 0;
	poscicionCabezal.cilindro = 0;
	poscicionCabezal.sector = 0;

	flagFin = 1;

	FD_ZERO(sockets);

	if (sockConsola)
		FD_SET(sockConsola, sockets);

	if (sockExterior)
		FD_SET(sockExterior, sockets);

	if (sockConsola > sockExterior)
		*sockMax = sockConsola;
	else
		*sockMax = sockExterior;

	return FIN_OK;
}
/*=====================================================================================*/
void verificarCola(void *ptr){

	while ((sockConsola || sockExterior) && flagFin){

		if (primerPedido){
			if (atenderPedido())
				flagFin = 0;
		}
		/*else
			sleep(1);*/
	}

	return;
}
/*=====================================================================================*/
int crearHiloAtencionPedidos(pthread_t *hilo){

	if (pthread_create(hilo, NULL, (void *) &verificarCola, NULL)){
		imprimirLog(ERROR, "No se pudo crear thread para el procesamiento de los pedidos", "");
		return FIN_ERROR;
	}

	return FIN_OK;
}
/*=====================================================================================*/
int abrirMapearArchivoPersistencia(char *archivoPersistencia, int *archivo){

	struct stat buf;

	if ((*archivo = open(archivoPersistencia, O_RDWR)) == -1){
		imprimirLog(ERROR, "No se pudo abrir el archivo para la persistencia de datos. open:", strerror(errno));
		return FIN_ERROR;
	}

	if (fstat(*archivo, &buf) == -1){
		imprimirLog(ERROR, "fstat:", strerror(errno));
		return FIN_ERROR;
	}

	tamanio = buf.st_size;

	if ((persistencia = mmap(0, tamanio, PROT_READ | PROT_WRITE, MAP_SHARED, *archivo, 0)) == MAP_FAILED){
		imprimirLog(ERROR, "mmap:", strerror(errno));
		return FIN_ERROR;
	}

	if (posix_madvise(persistencia, tamanio, POSIX_MADV_SEQUENTIAL) != 0){
		imprimirLog(ERROR, "posix_madvise:", "");
		return FIN_ERROR;
	}

	return FIN_OK;
}
/*=====================================================================================*/
int finalizarConexion(int sock, fd_set *sockets, int *sockMax){

	int i;
	int nuevoMaximo;
	struct EST_PEDIDOS *anterior;
	struct EST_PEDIDOS *actual;
	struct EST_PEDIDOS *ptr;

	FD_CLR(sock, sockets);	/*Saca el socket de la bolsa de sockets*/

	if (sock == sockExterior)	/*Cierra el socket*/
		cerrarSocket(&sockExterior);
	else
		cerrarSocket(&sockConsola);

	if(sock == *sockMax){	/*Cambia el socket maximo*/
		for(i = 0; i < *sockMax; i++)
			if(FD_ISSET(i, sockets))
				nuevoMaximo = i;

		*sockMax = nuevoMaximo;
	}

	anterior = NULL;
	actual = primerPedido;

	while (actual){ /*Sacar pedidos de la cola*/

		if (actual->sock == sock){

			if (actual->tipo == ESCRITURA)
				free(actual->buffer);

			if (anterior)
				anterior->siguiente = actual->siguiente;
			else
				primerPedido = actual->siguiente;

			ptr = actual;
			actual = actual->siguiente;
			free(ptr);
		}

		else{
			anterior = actual;
			actual = actual->siguiente;
		}
	}

	return FIN_OK;
}
/*=====================================================================================*/
int almacenarPedido(int sock, fd_set *sockets, int *sockMax){

	char *payload;
	struct HEADER_NIPC header;
	char *buffer;

	pthread_mutex_lock(&mutexSocket);
	if (recibirMensajeNIPCDinamico(sock, &header, &payload)){
		if (modoInicio == CONNECT || sock == sockConsola)
			finalizarConexion(sock, sockets, sockMax);
		else
			cerrarSocket(&sock);
		return FIN_OK;
	}
	pthread_mutex_unlock(&mutexSocket);

	if (header.type == LECTURA){
		pthread_mutex_lock(&mutexCola);
		agregarPedido(((struct PAYLOAD_LECTURA *) payload)->id, header.type, ((struct PAYLOAD_LECTURA *) payload)->sector, sock, NULL);
		pthread_mutex_unlock(&mutexCola);
	}

	else if (header.type == ESCRITURA){
		buffer = (char *) malloc(TAM_SECTOR * sizeof(char));
		memcpy(buffer, ((struct PAYLOAD_ESCRITURA *)payload)->buffer, TAM_SECTOR);

		pthread_mutex_lock(&mutexCola);
		agregarPedido(((struct PAYLOAD_ESCRITURA *) payload)->id, header.type, ((struct PAYLOAD_ESCRITURA *) payload)->sector, sock, buffer);
		pthread_mutex_unlock(&mutexCola);
	}

	else if (header.type == PRUEBA){
		pthread_mutex_lock(&mutexCola);
		agregarPedido((uint32_t) 0, header.type, /*(uint32_t) *payload*/ *((uint32_t *) payload), sock, NULL);
		pthread_mutex_unlock(&mutexCola);
	}

	else if (header.type == CANT_SECT){
		header.payloadLength = sizeof(struct EST_CHS);
		pthread_mutex_lock(&mutexSocket);
		if (enviarMensajeNIPC(sock, &header, (char *) &disco.chs)){
			pthread_mutex_unlock(&mutexSocket);
			if (modoInicio == CONNECT)
				finalizarConexion(sock, sockets, sockMax);
			else
				cerrarSocket(&sock);
			return FIN_OK;
		}
		pthread_mutex_unlock(&mutexSocket);

		if (modoInicio == LISTEN)
			cerrarSocket(&sock);
	}

	else if (header.type == POSICION){
		header.payloadLength = sizeof(struct EST_CHS);
		if (enviarMensajeNIPC(sock, &header, (char *) &poscicionCabezal)){
			finalizarConexion(sock, sockets, sockMax);
			return FIN_OK;
		}
	}

	else if (header.type == SALIR)
		flagFin = 0;

	else if (header.type == FIN_ESPEJADO){
		if ((msync(persistencia, tamanio, MS_SYNC)) == -1)
			imprimirLog(ERROR, "msync", "");
		imprimirLog(INFO, "Fin de espejado", "");
	}

	free(payload);

	return FIN_OK;
}
/*=====================================================================================*/
int recibirPedido(int sock, fd_set *sockets, int *sockMax){

	int sockNuevo;
	struct sockaddr address;

	if (modoInicio == LISTEN && sock == sockExterior){
		if (aceptarConexion(&sockExterior, &sockNuevo, &address)){
			finalizarConexion(sock, sockets, sockMax);
			return FIN_ERROR;
		}

		if (handshakePFS()){
			cerrarSocket(&sockNuevo);
			imprimirLog(WARNING, "Handshake fallido con PFS", "");
			return FIN_WARNING;
		}

		almacenarPedido(sockNuevo, sockets, sockMax);
	}
	else
		almacenarPedido(sock, sockets, sockMax);

	return FIN_OK;
}
/*=====================================================================================*/
int finalizarPrograma(pthread_t hilo, int archivo){

	struct EST_PEDIDOS *anterior;
	struct EST_PEDIDOS *ptr;

	pthread_join(hilo, NULL);

	pthread_mutex_destroy(&mutexCola);
	pthread_mutex_destroy(&mutexSocket);

	if ((msync(persistencia, tamanio, MS_SYNC)) == -1)
		imprimirLog(ERROR, "msync", "");

	if ((munmap(persistencia, tamanio)) == -1){
		imprimirLog(ERROR, "munmap:", strerror(errno));
		return FIN_ERROR;
	}

	if ((close(archivo)) == -1){
		imprimirLog(ERROR, "close:", strerror(errno));
		return FIN_ERROR;
	}

	anterior = NULL;

	while (primerPedido){ /*Sacar pedidos de la cola*/

		if (primerPedido->tipo == ESCRITURA)
			free(primerPedido->buffer);

		ptr = primerPedido;
		primerPedido = primerPedido->siguiente;
		free(ptr);
	}

	cerrarSocket(&sockExterior);
	cerrarSocket(&sockConsola);

	unlink(SERVER_PATH);

	pthread_mutex_destroy(&mutexLog);

	return FIN_OK;
}
